﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using PlayBox.DataClasses;

namespace PlayBox
{
    /// <summary>
    /// this class provides the EncryptionLayer that is used for all hard disk encryptions
    /// it can be used to store a song encrypted or serialize objects (encrypted) to the disk
    /// </summary>
    class EncryptionLayer
    {
        StreamWriter writer;
        StreamReader reader;

        private string _username = null;
        
        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="stream">the stream to use for reading/writing</param>
        /// <param name="salt">the salt, i.e. the filename</param>
        /// <param name="forReading">use the new object for reading or for writing</param>
        public EncryptionLayer(Stream stream, String salt,bool forReading)
        {
            initCrypto(stream, salt,forReading);
        }
        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="filepath">a path to a file that is read/written</param>
        /// <param name="forReading">use the new object for reading or for writing</param>
        public EncryptionLayer(String filepath,bool forReading)
            : this(new FileStream(filepath, FileMode.OpenOrCreate),Path.GetFileName(filepath),forReading)
        {
            
        }
        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="filepath">a path to a file that is read/written</param>
        /// <param name="username">the key of another user instead of the current user is used</param>
        /// <param name="forReading">use the new object for reading or for writing</param>
        public EncryptionLayer(String filepath, string username, bool forReading)
        {
            _username = username;
            initCrypto(new FileStream(filepath, FileMode.OpenOrCreate), 
                Path.GetFileName(filepath), forReading);
        }
        /// <summary>
        /// gets the IV from a given salt string
        /// </summary>
        /// <param name="salt">the salt string</param>
        /// <returns>an IV that can be used for the AES CBC encryption</returns>
        private byte[] getIV(string salt)
        {
            byte[] hash;
            byte[] IV = new byte[128/8];

            byte[] buffer = Encoding.ASCII.GetBytes(salt);
            SHA1CryptoServiceProvider SHA1 = new SHA1CryptoServiceProvider();
            hash = SHA1.ComputeHash(buffer);

            //copy desired bytes
            for (int i = 0; i < 128 / 8; i++)
            {
                IV[i] = hash[i];
            }

            return IV;
        }
        /// <summary>
        /// initializes basic crypto settings
        /// </summary>
        /// <param name="stream">the stream to use for de/encryption</param>
        /// <param name="salt">the salt to derive the IV from</param>
        /// <param name="forReading">use this object for reading/writing</param>
        private void initCrypto(Stream stream, String salt, bool forReading)
        {
            string currentUser = "";

            if (_username != null)
                currentUser = _username;
            else if (User.IsLoggedIn)
                currentUser = User.UserData.Username;
            else
                throw new Exception("No user provided");

      
            byte[] IV = getIV(salt);

            RijndaelManaged AES = new RijndaelManaged();
            AES.Mode = CipherMode.CBC;
            AES.BlockSize = 128;
            AES.Padding = PaddingMode.PKCS7;
            byte[] KeyBytes = DataClasses.TPM.getUserKey(currentUser);

            writer = null;
            reader = null;

            if (!forReading)
            {
                ICryptoTransform Encryptor = AES.CreateEncryptor(KeyBytes, IV);
                CryptoStream wstream = new CryptoStream(stream, Encryptor, CryptoStreamMode.Write);
                writer = new StreamWriter(wstream);
            }
            else
            {
                ICryptoTransform Decryptor = AES.CreateDecryptor(KeyBytes, IV);
                CryptoStream rstream = new CryptoStream(stream, Decryptor, CryptoStreamMode.Read);
                reader = new StreamReader(rstream);
            }
        }
        /// <summary>
        /// reads the next line from the file
        /// </summary>
        /// <returns>the next line</returns>
        public String readLine()
        {
            if (reader == null)
            {
                throw new Exception("Cannot read as EncryptionLayer was initialized for writing!");
            }
            return reader.ReadLine();
        }
        /// <summary>
        /// write a line to the encrypted file
        /// </summary>
        /// <param name="str">a line</param>
        public void writeLine(String str)
        {
            if (writer == null)
            {
                throw new Exception("Cannot write as EncryptionLayer was initialized for reading!");
            }
            writer.WriteLine(str);
        }
        /// <summary>
        /// gets a MemoryStream of the encrypted file (decrypts the whole file into memory)
        /// </summary>
        /// <returns>a MemoryStream</returns>
        public Stream getMemoryStream()
        {
            if (reader == null)
            {
                throw new Exception("Cannot read as EncryptionLayer was initialized for writing!");
            }
            Stream stream = new MemoryStream();
            reader.BaseStream.CopyTo(stream);
            return stream;
        }
        /// <summary>
        /// writes the given stream to disk (encrypted)
        /// </summary>
        /// <param name="stream">a stream</param>
        public void writeStream(Stream stream)
        {
            if (writer == null)
            {
                throw new Exception("Cannot write as EncryptionLayer was initialized for reading!");
            }
            stream.CopyTo(writer.BaseStream);
        }
        /// <summary>
        /// closes the stream and frees up ressources
        /// </summary>
        public void close()
        {
            if (writer != null)
            {
                writer.Close();
            }
            if (reader != null)
            {
                reader.Close();
            }
        }
        /// <summary>
        /// provides the possibility to serialize an object and store it encrypted on the disk
        /// </summary>
        /// <param name="obj">the object to serialize</param>
        public void Serialize(object obj)
        {
            if (writer == null)
            {
                throw new Exception("Cannot write as EncryptionLayer was initialized for reading!");
            }
            Stream stream = writer.BaseStream;
            BinaryFormatter bformatter = new BinaryFormatter();
            bformatter.Serialize(stream, obj);
        }
        /// <summary>
        /// deserializes an encrypted object
        /// </summary>
        /// <returns>the deserializes object</returns>
        public object Deserialize()
        {
            if (reader == null)
            {
                throw new Exception("Cannot read as EncryptionLayer was initialized for writing!");
            }
            Stream stream = reader.BaseStream;
            BinaryFormatter bformatter = new BinaryFormatter();
            return bformatter.Deserialize(stream);
        }
    }
}
